package main

import (
	"fmt"
	"sync"
	"time"
)

// sync 包低级同步原语使用场景
// 首先是需要高性能的临界区(critical season)同步机制场景
// 不想转移结构体对象所有权，但又要保证结构体内部状态数据的同步访问场景

// sync 包注释:
// mutex.go
// Values containing the types defined in this package should not be copied
// 不要复制那些包含了此包中类型的值.

// 如果对使用过的、sync包中的类型的示例进行复制，并使用了复制后的副本，将产生不可预期的结果。
// 在使用sync包中的类型的时候，推荐通过闭包方式，或者是传递类型实例(或包裹该类型的类型实例)的指针的方式进行

// 互斥锁两个操作原则
// 1、尽量减少在锁中的操作，这可以减少其他因Goroutine阻塞而带来的损耗与延迟
// 2、一定要记得调用Unlock解锁。 记解锁会导致程序局部死锁，甚至是整个程序死锁，会导致严重的后果。、

// 条件变量
// 可以把条件变量理解为一个容器，这个容器存放着一个或一组等待着某个条件成立的Goroutinue. 当条件成立后，这些等待条件成立的Goroutinue将得到通知，并被唤醒继续后续的工作。

// 面向 CSP 并发模型的 channel 原语和面向传统共享内存并发模型的 sync 包提供的原语，已经能够满足 Go 语言应用并发设计中 99.9% 的并发同步需求了。
// 而剩余那 0.1% 的需求，可以使用 Go 标准库提供的 atomic 包来实现。
// atomic 原子操作可用来同步的范围有比较大限制，只能同步一个整型变量或自定义类型变量。
// 如果要对一个复杂的临界区数据进行同步，那么首选的依旧是 sync 包中的原语。
func worker(i int) {
	fmt.Printf("worker %d: is working...\n", i)
	time.Sleep(time.Second)
	fmt.Printf("worker %d: works done\n", i)
}

type signal struct{}

var ready bool

func spawnGroup(f func(i int), num int, mu *sync.Mutex) <-chan signal {
	c := make(chan signal)
	var wg sync.WaitGroup

	for i := 0; i < num; i++ {
		wg.Add(1)
		go func(i int) {
			for {
				mu.Lock()
				if !ready {
					mu.Unlock()
					time.Sleep(100 * time.Millisecond)
					continue
				}
				mu.Unlock()
				fmt.Printf("worker %d: start to work.\n", i)
				f(i)
				wg.Done()
				return
			}
		}(i + 1)
	}

	go func() {
		wg.Wait()
		c <- signal(struct{}{})
	}()
	return c
}

func spawnGroupCond(f func(i int), num int, groupSignal *sync.Cond) <-chan signal {
	c := make(chan signal)
	var wg sync.WaitGroup

	for i := 0; i < num; i++ {
		wg.Add(1)
		go func(i int) {
			groupSignal.L.Lock()
			if !ready {
				// 加锁后，各个等待条件成立的 Goroutine 判断条件是否成立，
				// 如果不成立，则调用sync.Cond的 Wait 方法进入等待状态。Wait 方法在 Goroutine 挂起前会进行 Unlock 操作。
				groupSignal.Wait()
			}
			groupSignal.L.Unlock()
			fmt.Printf("worker %d: start to work.\n", i)
			f(i)
			wg.Done()
		}(i + 1)
	}

	go func() {
		wg.Wait()
		c <- signal(struct{}{})
	}()
	return c
}

func main() {
	fmt.Println("start a group of workers...")
	mu := &sync.Mutex{}
	c := spawnGroup(worker, 5, mu)

	time.Sleep(5 * time.Second) // 模拟ready前的准备工作
	fmt.Println("the group of workers start to work...")

	mu.Lock()
	ready = true
	mu.Unlock()

	// 轮询开销大，轮询间隔设置的不同，条件检查的及时性也会收到影响
	// sync.Cond 为 Goroutine 在这个场景提供了另一种可选的、资源消耗更小、使用体验更佳的同步方式
	// 使用条件变量原语， 可以在实现目标的同时，避免对条件的轮询
	<-c
	ready = false
	fmt.Println("the group of workers work done!")

	fmt.Println("-------------------------------")

	fmt.Println("start a group of workers...")
	groupSignal := sync.NewCond(&sync.Mutex{})
	c = spawnGroupCond(worker, 5, groupSignal)

	time.Sleep(5 * time.Second) // 模拟ready前的准备工作
	fmt.Println("the group of workers start to work...")

	groupSignal.L.Lock()
	ready = true
	// 当 main goroutine 将ready置为 true，并调用sync.Cond的 Broadcast 方法后，各个阻塞的 Goroutine 将被唤醒，
	//并从 Wait 方法中返回。Wait 方法返回前，Wait 方法会再次加锁让 Goroutine 进入临界区。
	// 接下来 Goroutine 会再次对条件数据进行判定，如果条件成立，就会解锁并进入下一个工作阶段；
	// 如果条件依旧不成立，那么会再次进入循环体，并调用 Wait 方法挂起等待。
	groupSignal.Broadcast()
	groupSignal.L.Unlock()

	// 轮询开销大，轮询间隔设置的不同，条件检查的及时性也会收到影响
	// sync.Cond 为 Goroutine 在这个场景提供了另一种可选的、资源消耗更小、使用体验更佳的同步方式
	// 使用条件变量原语， 可以在实现目标的同时，避免对条件的轮询
	<-c
	ready = false
	fmt.Println("the group of workers work done!")
}
